﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.Framework;
using BesAsm.Framework.Reporting;
using BesAsm.ModelData.Data;
using BesAsm.ModelData.Entities;
using BesAsm.ModelData.Services;
using BesAsm.MasterData.Services;

namespace BesAsm.Emgaats.Core
{
  /// <summary>
  /// Delegate for the ModelStateChanged event
  /// </summary>
  /// <param name="sender">The sender.</param>
  /// <param name="e">The ModelStateChangedArgs</param>
  public delegate void ModelStateChangedHandler(object sender, ModelStateChangedArgs e);

  /// <summary>
  /// An Emgaats model
  /// </summary>
  public class Model
  {
    private const string DEPLOY_HYDRAULICS_RESOURCE = "BesAsm.Emgaats.ModelBuilder.ModelDeployHydraulics.mdb";
    private const string DEPLOY_HYDROLOGY_RESOURCE = "BesAsm.Emgaats.ModelBuilder.ModelDeployHydrology.mdb";
    private const string DEPLOY_EMGAATSCODE_RESOURCE = "BesAsm.Emgaats.ModelBuilder.EmgaatsCode.mdb";
    private const string EMGAATS_CONFIG_FILE_EXTENSION = ".emgaats";
    private const string CONTROL_MXD_FILE_EXTENSION = ".emgaats.mxd";
    private const string LOG_FILE_EXTENSION = ".emgaats.log";
    private const string EMGAATS_PGDB_FILE_EXTENSION = ".mdb";

    private string _controlMXD = String.Empty;
    private DateTime _createdDate = DateTime.MinValue;
    private string _createdVia = String.Empty;
    private string _description = String.Empty;
    private List<ReportableMessage> _errors = new List<ReportableMessage>();
    private EmgaatsLayerCollection _modelLayers;
    private Dictionary<LibraryItem, string> _modelLayerNames;
    private string _modelLogFile = String.Empty;
    private string _modelSystemVersion = String.Empty;
    private ModelState _modelState;
    private DateTime _modifiedDate = DateTime.MinValue;
    private string _name = string.Empty;
    private string _path = string.Empty;
    private List<Simulation> _simulations = new List<Simulation>();
    private List<int> _startLinks = null;
    private ModelState _state = ModelState.Null;
    private List<int> _stopLinks = null;
    private string _timeFrame = String.Empty;

    private XmlSerializer _serializer;

    private EmgaatsList<ModelDsc> _modelDscs = null;
    private EmgaatsList<ModelLink> _modelLinks = null;
    private EmgaatsList<ModelNode> _modelNodes = null;
    private EmgaatsList<ModelParkingTarget> _modelParkingTargets = null;
    private EmgaatsList<ModelRoofTarget> _modelRoofTargets = null;
    private EmgaatsList<ModelSsc> _modelSscs = null;
    private EmgaatsList<ModelStreetTarget> _modelStreetTargets = null;

    #region Constructors
    /// <summary>
    /// Creates a null model
    /// </summary>
    public Model()
    {
      _modelState = ModelState.Null;
      _serializer = new XmlSerializer(this.GetType());      
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Model"/> class.
    /// </summary>
    /// <param name="path">The path to the model root directory. The model name will
    /// be set to the last folder in this path</param>
    public Model(string path) : this()
    {
      InitializeModel(path);    
    }
    #endregion

    #region Persistent properties
    /// <summary>
    /// The system/database version upon which the model is based
    /// </summary>
    public string ModelSystemVersion
    {
      get
      {
        return _modelSystemVersion;
      }
      set
      {
        _modelSystemVersion = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("ModelSystemVersion");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The MXD specified by the user to help manipulate the model
    /// </summary>
    public string ControlMXD
    {
      get
      {
        return _controlMXD;
      }
      set
      {
        _controlMXD = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("ControlMXD");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The name of the log file used to record logging and event messages for the model
    /// </summary>
    public string LogFile
    {
      get
      {
        return _modelLogFile;
      }
      set
      {
        _modelLogFile = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("ModelLogFile");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The date the model was created
    /// </summary>
    public DateTime Created
    {
      get
      {
        return _createdDate;
      }
      set
      {
        _createdDate = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("Created");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The date the model was last modified
    /// </summary>
    public DateTime Modified
    {
      get
      {
        return _modifiedDate;
      }
      set
      {
        _modifiedDate = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("Modified");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The list of links forming the most downstream edges of the model
    /// </summary>
    public List<int> StartLinks
    {
      get
      {
        return _startLinks;
      }
      set
      {
        _startLinks = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("StartLinks");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The list of links forming the most upstream edges of the model
    /// </summary>
    public List<int> StopLinks
    {
      get
      {
        return _stopLinks;
      }
      set
      {
        _stopLinks = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("StopLinks");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The hydrologic condition of the model
    /// </summary>
    public string TimeFrame
    {
      get
      {
        return _timeFrame;
      }
      set
      {
        _timeFrame = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("TimeFrame");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// The current state of the model
    /// </summary>
    public ModelState State
    {
      get
      {
        return _state;
      }
      set
      {
        _state = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("State");
        OnPropertyChanged(args);
        OnModelStateChanged(new ModelStateChangedArgs(this, _modelState));
      }
    }

    /// <summary>
    /// The name of the model
    /// </summary>
    public string Name
    {
      get
      {
        return _name;
      }
      set
      {
        _name = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("Name");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// A description of the model's purpose
    /// </summary>
    public string Description
    {
      get
      {
        return _description;
      }
      set
      {
        _description = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("Description");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// A description of the mechanism used to create the model
    /// </summary>
    public string CreatedVia
    {
      get
      {
        return _createdVia;
      }
      set
      {
        _createdVia = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("CreatedVia");
        OnPropertyChanged(args);
      }
    }

    /// <summary>
    /// A list of the simulations designated for the model
    /// </summary>
    public List<Simulation> Simulations
    {
      get
      {
        return _simulations;
      }
      set
      {
        _simulations = value;
        PropertyChangedEventArgs args = new PropertyChangedEventArgs("Simulations");
        OnPropertyChanged(args);
      }
    }
    #endregion

    /// <summary>
    /// True if the model should not be modified
    /// </summary>
    [XmlIgnore]
    public bool Locked { get; set; }

    /// <summary>
    /// True if the model has been modified since the last save
    /// </summary>
    [XmlIgnore]
    public bool Dirty { get; set; }

    #region Error Handling
    public List<ReportableMessage> Errors
    {
      get
      {
        return _errors;
      }
    }
    #endregion

    #region IO services
    public void InitializeModel(string modelPath)
    {
      _path = modelPath;
      Name = modelPath.Split(System.IO.Path.DirectorySeparatorChar).Last();
      Created = DateTime.Now;
      ControlMXD = System.IO.Path.Combine(modelPath,Name + CONTROL_MXD_FILE_EXTENSION);
      ModelSystemVersion = Mediator.SystemVersion;

      SetModelLayers();
    }

    public void SetModelLayers()
    {
      _modelLayerNames = new Dictionary<LibraryItem, string>();
      _modelLayers = new EmgaatsLayerCollection();
      SetModelLayerNames();
    }

    /// <summary>
    /// Gets the path to the model root folder
    /// </summary>
    public string Path
    {
      get { return _path; }
    }

    /// <summary>
    /// Gets the full absolute path to one of the model files. An exception will
    /// be thrown if the Model does not have a path or name assigned.
    /// </summary>
    /// <param name="modelComponent">The model component to whose path to retrieve</param>
    /// <returns>The path to the specified model compoment</returns>
    public virtual string GetPathIfSet(ModelPathComponent modelComponent)
    {
      if (_path == string.Empty)
        throw new FileNotFoundException("Model path is not specified");

      String[] pathComponents = _path.Split(System.IO.Path.DirectorySeparatorChar);
      string modelName = pathComponents.Last();

      switch (modelComponent)
      {
        case ModelPathComponent.ConfigFile:
          return System.IO.Path.Combine(_path, modelName + EMGAATS_CONFIG_FILE_EXTENSION);
          break;
        case ModelPathComponent.Pgdb:
          if (_name == string.Empty)
            throw new FileNotFoundException("Model name is not specified");
          return System.IO.Path.Combine(_path, modelName + EMGAATS_PGDB_FILE_EXTENSION);
          break;
        case ModelPathComponent.RootFolder:
          return _path;
          break;
        case ModelPathComponent.ControlMXD:
          string controlMXDFileName =
            ControlMXD.Length == 0 ?
              System.IO.Path.Combine(_path, modelName + CONTROL_MXD_FILE_EXTENSION) :
              ControlMXD;
          return controlMXDFileName;
          break;
        case ModelPathComponent.LogFile:
          string logFileName =
            LogFile.Length == 0 ?
              System.IO.Path.Combine(_path, modelName + LOG_FILE_EXTENSION) :
              LogFile;
          return logFileName;
          break;
        default:
          throw new ArgumentException("Invalid ModelComponent");
      }
    }

    public static string GetConfigFileName(string modelPath)
    {
      string lastModelPathItem = modelPath.Split(System.IO.Path.DirectorySeparatorChar).Last();
      string fileName = string.Format("{0}\\{1}{2}", modelPath, lastModelPathItem,
        EMGAATS_CONFIG_FILE_EXTENSION);
      return fileName;
    }

    #endregion IO Services

    #region Layer Management
    private void SetModelLayerNames()
    {
      //TODO: Read this from config file
      _modelLayerNames.Add(LibraryItem.ModelLinks, "MODEL_LINKS");
      _modelLayerNames.Add(LibraryItem.ModelNodes, "MODEL_NODES");
      _modelLayerNames.Add(LibraryItem.ModelDscs, "MODEL_DSCS");
      _modelLayerNames.Add(LibraryItem.ModelSscs, "MODEL_SSCS");
      _modelLayerNames.Add(LibraryItem.ModelParkingTargets, "MODEL_PARKING_TARGETS");
      _modelLayerNames.Add(LibraryItem.ModelRoofTargets, "MODEL_ROOF_TARGETS");
      _modelLayerNames.Add(LibraryItem.ModelStreetTargets, "MODEL_STREET_TARGETS");
    }

    /// <summary>
    /// Gets a model layer.
    /// </summary>
    /// <param name="libraryItem">The library item to retrieve</param>
    /// <returns></returns>
    public EmgaatsLayer GetModelLayer(LibraryItem libraryItem)
    {
      if (!LayerLibrarian.ModelLibraryItems.Contains(libraryItem))
        throw new ArgumentException("LibraryItem " + libraryItem.ToString() + " is not a model layer");

      if (!_modelLayers.Contains(libraryItem))
        throw new ArgumentException("LibraryItem " + libraryItem.ToString() + " is not present in the model");

      return _modelLayers[libraryItem];
    }

    /// <summary>
    /// Gets a copy of the the model layers collection.
    /// </summary>
    [XmlIgnore]
    public EmgaatsLayerCollection ModelLayers
    {
      get { return new EmgaatsLayerCollection(_modelLayers); }
    }
    #endregion

    /// <summary>
    /// Checks for whether the supplied state is a valid model state to switch to
    /// </summary>
    /// <param name="nextModelState">The next ModelState to check</param>
    /// <returns>True if supplied ModelState is valid</returns>
    public bool IsNextStateValid(ModelState nextModelState)
    {
      switch (_modelState)
      {
        case ModelState.Null:
          return nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.TemplateApplied:
          return nextModelState == ModelState.HasNetwork;
          break;
        case ModelState.HasNetwork:
          return nextModelState == ModelState.HasDscs ||
            nextModelState == ModelState.HasSscs ||
            nextModelState == ModelState.FullyTraced ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.HasDscs:
          return nextModelState == ModelState.HasSscs ||
            nextModelState == ModelState.FullyTraced ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.HasSscs:
          return nextModelState == ModelState.HasDscs ||
            nextModelState == ModelState.FullyTraced ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.FullyTraced:
          return nextModelState == ModelState.Specified ||
            nextModelState == ModelState.TemplateApplied;
        case ModelState.Specified:
          return nextModelState == ModelState.FullyTraced ||
            nextModelState == ModelState.Assembled ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.Assembled:
          return nextModelState == ModelState.Specified ||
            nextModelState == ModelState.Deployed ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.Deployed:
          return nextModelState == ModelState.Assembled ||
            nextModelState == ModelState.Simulated ||
            nextModelState == ModelState.TemplateApplied;
          break;
        case ModelState.Simulated:
          return nextModelState == ModelState.Deployed ||
            nextModelState == ModelState.TemplateApplied;
          break;
        default:
          return false;
      }
    }

    #region Events
    /// <summary>
    /// Occurs when the Model state changed
    /// </summary>
    public event ModelStateChangedHandler ModelStateChanged;

    protected virtual void OnModelStateChanged(ModelStateChangedArgs e)
    {
      if (ModelStateChanged != null)
        ModelStateChanged(this, e);
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
      if (PropertyChanged != null)
        PropertyChanged(this, e);
      _modifiedDate = DateTime.Now;
      Dirty = true;
    }
    #endregion

    #region Testing
    /// <summary>
    /// For testing
    /// </summary>
    /// <param name="modelPath"></param>
    internal void SetPath(string modelPath)
    {
      _path = modelPath;
    }

    #endregion

    /// <summary>
    /// Gets the name of a model layer.
    /// </summary>
    /// <param name="libraryItem">The model-data library item whose name to retrieve.</param>
    /// <returns>The name of the library item</returns>
    public string GetModelLayerName(LibraryItem libraryItem)
    {

      if (!LayerLibrarian.ModelLibraryItems.Contains(libraryItem))
        throw new ArgumentException("LibraryItem " + libraryItem.ToString() + " is not a model layer");

      if (!_modelLayerNames.ContainsKey(libraryItem))
        throw new ArgumentException("LibraryItem " + libraryItem.ToString() + " does not have a layer name assigned");

      return _modelLayerNames[libraryItem];
    }   

    #region Model Features and Data    

    public void SetFeature(
      IApplication arcApp, 
      EmgaatsLayer item)
    {
      PropertyChangedEventArgs args = null;
      if (!LayerLibrarian.ModelLibraryItems.Contains(item.LibraryItem))
        return;
      Messenger.ReportMessage("Preparing layer " + item.LibraryItem.ToString(),
        ReportableMessageType.Info);

      switch (item.LibraryItem)
      {
        case LibraryItem.ModelDscs:
          _modelDscs = new EmgaatsList<ModelDsc>(arcApp, item);
          _modelDscs.SyncListToMap(new ModelDscService());
          args = new PropertyChangedEventArgs("Dscs");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelLinks:
          _modelLinks = new EmgaatsList<ModelLink>(arcApp, item);
          _modelLinks.SyncListToMap(new ModelLinkService());
          args = new PropertyChangedEventArgs("Links");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelNodes:
          _modelNodes = new EmgaatsList<ModelNode>(arcApp, item);
          _modelNodes.SyncListToMap(new ModelNodeService());
          args = new PropertyChangedEventArgs("Nodes");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelParkingTargets:
          _modelParkingTargets = new EmgaatsList<ModelParkingTarget>(arcApp, item);
          _modelParkingTargets.SyncListToMap(new ModelParkingTargetService());
          args = new PropertyChangedEventArgs("ParkingTargets");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelRoofTargets:
          _modelRoofTargets = new EmgaatsList<ModelRoofTarget>(arcApp, item);
          _modelRoofTargets.SyncListToMap(new ModelRoofTargetService());
          args = new PropertyChangedEventArgs("RoofTargets");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelSscs:
          _modelSscs = new EmgaatsList<ModelSsc>(arcApp, item);
          _modelSscs.SyncListToMap(new ModelSscService());
          args = new PropertyChangedEventArgs("Sscs");
          OnPropertyChanged(args);
          break;
        case LibraryItem.ModelStreetTargets:
          _modelStreetTargets = new EmgaatsList<ModelStreetTarget>(arcApp, item);
          _modelStreetTargets.SyncListToMap(new ModelStreetTargetService());
          args = new PropertyChangedEventArgs("StreetTargets");
          OnPropertyChanged(args);
          break;
      }
    }

    public void SetFeatures(IApplication arcApp, LayerLibrarian librarian)
    {
      try
      {
        foreach (EmgaatsLayer item in librarian)
        {
          if (!LayerLibrarian.ModelLibraryItems.Contains(item.LibraryItem))
            continue;
          SetFeature(arcApp, item);
        }
      }
      catch (Exception ex)
      {
        throw ex;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Model links
    /// </summary>
    public EmgaatsList<ModelLink> Links
    {
      get
      {
        return _modelLinks;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Gets the model nodes.
    /// </summary>
    public EmgaatsList<ModelNode> Nodes
    {
      get
      {
        return _modelNodes;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Model direct subcatchments
    /// </summary>
    public EmgaatsList<ModelDsc> Dscs
    {
      get
      {
        return _modelDscs;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Gets the model SSCS.
    /// </summary>
    public EmgaatsList<ModelSsc> Sscs
    {
      get
      {
        return _modelSscs;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Gets the model parking targets.
    /// </summary>
    public EmgaatsList<ModelParkingTarget> ParkingTargets
    {
      get
      {
        return _modelParkingTargets;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Gets the model roof targets.
    /// </summary>
    public EmgaatsList<ModelRoofTarget> RoofTargets
    {
      get
      {
        return _modelRoofTargets;
      }
    }

    [XmlIgnore]
    /// <summary>
    /// Gets the model street targets.
    /// </summary>
    public EmgaatsList<ModelStreetTarget> StreetTargets
    {
      get
      {
        return _modelStreetTargets;
      }
    }
    #endregion
  }
}
